package com.test;

import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
* @ClassName: HttpsServiceUtil
* @Description: TODO(这里用一句话描述这个类的作用)
* @author luoyl
* @date 2016年12月19日 下午8:37:05
*
*/ 
public class HttpsServiceUtil {
    private static final Logger logger = LoggerFactory.getLogger(HttpsServiceUtil.class);

    //表示请求器是否已经做了初始化工作
    private boolean hasInit = false;

    private int connectRequestTimeout = 30000;
    //连接超时时间，默认30秒
    private int socketTimeout = 30000;
    //传输超时时间，默认30秒
    private int connectTimeout = 30000;

    private String keyStoreType = KeyStore.getDefaultType();

    private String certPath;

    private String certPassW;

    private boolean isHttps;

    private PoolingHttpClientConnectionManager cm;

    private RequestConfig requestConfig;

    public HttpsServiceUtil() throws UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException, KeyStoreException, IOException {
        init();
    }

    public HttpsServiceUtil(int connectRequestTimeout, int socketTimeout, int connectTimeout, String keyStoreType, String certPath, String certPassW, boolean isHttps)
            throws UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException, KeyStoreException, IOException {
        this.connectRequestTimeout = connectRequestTimeout;
        this.socketTimeout = socketTimeout;
        this.connectTimeout = connectTimeout;
        this.keyStoreType = keyStoreType;
        this.certPath = certPath;
        this.certPassW = certPassW;
        this.isHttps = isHttps;
        init();
    }

    private void init() throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException {
        logger.info("init#初始化Http请求连接池");
        RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.getSocketFactory());
        if (certPath != null && isHttps) {    //是否需要支持https双向认证
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            InputStream instream = HttpsServiceUtil.class.getClassLoader().getResourceAsStream(certPath);
            try {
                keyStore.load(instream, certPassW.toCharArray());//设置证书密码
            } catch (CertificateException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } finally {
                instream.close();
            }
            // Trust own CA and all self-signed certs
            SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, certPassW.toCharArray()).build();
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
            registryBuilder = registryBuilder.register("https", sslsf);
        }

        if (certPath == null && isHttps) {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            SSLContext sslContext = SSLContexts.custom().useTLS().loadTrustMaterial(trustStore, new TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            }).build();
            LayeredConnectionSocketFactory layersf = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            registryBuilder = registryBuilder.register("https", layersf);
        }

        Registry<ConnectionSocketFactory> registry = registryBuilder.build();
        cm = new PoolingHttpClientConnectionManager(registry);
        // 将最大连接数增加到200
        cm.setMaxTotal(150);
        // 将每个路由基础的连接增加到20
        cm.setDefaultMaxPerRoute(150);
        // 将目标主机的最大连接数增加到50

        //请求器的配置
        requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).setConnectionRequestTimeout(connectRequestTimeout).build();
        hasInit = true;
    }

    public CloseableHttpClient getConnection() throws Exception {
        if (!hasInit || cm == null) {
            init();
        }
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(requestConfig).build();
        if (cm != null && cm.getTotalStats() != null) {
            logger.info("getConnection#now client pool#total_status:{}", cm.getTotalStats().toString());
        }
        return httpClient;
    }

    /**
     * 通过Https往API post xml数据
     *
     * @param url    API地址
     * @param xmlObj 要提交的XML数据对象
     * @return API回包的实际数据
     * @throws IOException
     * @throws KeyStoreException
     * @throws UnrecoverableKeyException
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */

    public String sendPost(String url, String xmlObj, String charset) throws Exception {
        //HTTP请求器
        CloseableHttpClient httpClient = getConnection();

        HttpPost httpPost = new HttpPost(url);
        //得指明使用UTF-8编码，否则到API服务器XML的中文不能被成功识别
        StringEntity postEntity = new StringEntity(xmlObj, charset);
        httpPost.addHeader("Content-Type", "text/xml");
        postEntity.setContentEncoding(charset);
        httpPost.setEntity(postEntity);

        //设置请求器的配置
        //httpPost.setConfig(requestConfig);
        CloseableHttpResponse response = null;
        String result = null;
        try {
            response = httpClient.execute(httpPost);
            if (response != null) {
                HttpEntity entity = response.getEntity();
                result = EntityUtils.toString(entity, charset);
            }
        } catch (ConnectionPoolTimeoutException e) {
            logger.error("sendPost#http请求异常:", e);
            throw e;
        } catch (ConnectTimeoutException e) {
            logger.error("sendPost#请求连接超时:", e);
            throw e;
        } catch (SocketTimeoutException e) {
            logger.error("sendPost#socket请求超时:", e);
            throw e;
        } catch (Exception e) {
            logger.error("sendPost#http请求异常:", e);
            throw e;
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
                httpPost.abort();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return result;
    }

    /**
     * 设置连接超时时间
     *
     * @param socketTimeout 连接时长，默认10秒
     */
    public void setSocketTimeout(int socketTimeout) {
        socketTimeout = socketTimeout;
        resetRequestConfig();
    }

    /**
     * 设置传输超时时间
     *
     * @param connectTimeout 传输时长，默认30秒
     */
    public void setConnectTimeout(int connectTimeout) {
        connectTimeout = connectTimeout;
        resetRequestConfig();
    }

    private void resetRequestConfig() {
        requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
    }

    /**
     * 允许商户自己做更高级更复杂的请求器配置
     *
     * @param requestConfig 设置HttpsRequest的请求器配置
     */
    public void setRequestConfig(RequestConfig requestConfig) {
        requestConfig = requestConfig;
    }

}